#include "DefaultUI.h"

using namespace OBR;

/**
 * @brief use OpenCV to display an image
 *
 * @param img
 * @param width
 * @param height
 */
void OBR::showImageCV(Spectrum *fbo, int width, int height, std::string name, cv::Mat &window,
                      std::string desc)
{
#pragma omp parallel for
    for (int x = 0; x < width; x++)
    {
        for (int y = 0; y < height; y++)
        {
            int idx = y * width + x;
            char4 color = ColorTools::radiance2color(fbo[idx], 2.2);
            window.at<cv::Vec3b>(y, x) = cv::Vec3b(color.x, color.y, color.z);
        }
    }
    cv::cvtColor(window, window, cv::COLOR_RGB2BGR);
    cv::putText(window, desc, cv::Point2i(4, 16), cv::FONT_HERSHEY_TRIPLEX, 0.5,
                cv::Scalar(255, 255, 255), 1, 8, false);
    cv::imshow(name, window);
    cv::waitKey(1);
}

/**
 * @brief use OpenCV to save an image
 *
 * @param img
 * @param width
 * @param height
 */
void OBR::saveImageCV(Spectrum *fbo, int width, int height, std::string name)
{
    cv::Mat window(height, width, CV_8UC3, cv::Scalar(0));
    for (size_t x = 0; x < width; x++)
    {
        for (size_t y = 0; y < height; y++)
        {
            int idx = y * width + x;
            char4 color = ColorTools::radiance2color(fbo[idx], 2.2);
            window.at<cv::Vec3b>(y, x) = cv::Vec3b(color.x, color.y, color.z);
        }
    }
    cv::cvtColor(window, window, cv::COLOR_RGB2BGR);
    cv::imwrite(name, window);
}

/**
 * @brief opencv mouse callback
 *
 * @param event
 * @param x
 * @param y
 * @param flags
 * @param userdata
 */
void __defaultMouseHandler(int event, int x, int y, int flags, void *userdata)
{
    const float rotate_speed = 0.8;
    const float translate_speed = 4.0;
    const float zoom_speed = 2e-3;
    CVMouseHandlerStates *states = reinterpret_cast<CVMouseHandlerStates *>(userdata);

    if (!states->enable)
    {
        return;
    }

    // adjust btn state
    if (event == cv::EVENT_LBUTTONUP && states->left_btn_down)
    {
        states->left_btn_down = false;
    }
    else if (event == cv::EVENT_LBUTTONDOWN && !states->left_btn_down)
    {
        states->left_btn_down = true;
        states->prev_pos = make_float2(-1);
    }
    if (event == cv::EVENT_RBUTTONUP && states->right_btn_down)
    {
        states->right_btn_down = false;
    }
    else if (event == cv::EVENT_RBUTTONDOWN && !states->right_btn_down)
    {
        states->right_btn_down = true;
        states->prev_pos = make_float2(-1);
    }
    // react to rotate
    if (states->left_btn_down && (event == cv::EVENT_MOUSEMOVE || event == cv::EVENT_LBUTTONUP))
    {
        float2 curr_pos = make_float2(x, y);
        if (states->prev_pos.x != -1 && states->prev_pos.y != -1)
        {
            float2 deg = (curr_pos - states->prev_pos) / states->window_size;
            deg.x = -deg.x;
            deg *= make_float2(M_PI, M_PI_2);
            deg *= rotate_speed;
            states->sc->camera->rotate(deg);
            states->sc->has_update = true;
        }
        states->prev_pos = curr_pos;
    }
    // react to translate
    if (states->right_btn_down && (event == cv::EVENT_MOUSEMOVE || event == cv::EVENT_RBUTTONUP))
    {
        float2 curr_pos = make_float2(x, y);
        if (states->prev_pos.x != -1 && states->prev_pos.y != -1)
        {
            float2 dir =
                (curr_pos - states->prev_pos) / max(states->window_size.x, states->window_size.y);
            dir.x = -dir.x;
            dir *= translate_speed;
            states->sc->camera->translate(dir);
            states->sc->has_update = true;
        }
        states->prev_pos = curr_pos;
    }
    // react to scroll
    if (event == cv::EVENT_MOUSEWHEEL)
    {
        float zoom_dist = zoom_speed * cv::getMouseWheelDelta(flags);
        states->sc->camera->zoom(zoom_dist);
        states->sc->has_update = true;
    }
}

cv::Mat DefaultUI::window;
CVMouseHandlerStates DefaultUI::states;
bool DefaultUI::initialized = false;
std::string DefaultUI::curr_title = "";
const std::string DefaultUI::__defualt_ui_window_name = " __defualt_ui_window__";

void DefaultUI::reset(std::string title, uint w, uint h)
{
    if (!DefaultUI::initialized)
    {
        cv::destroyAllWindows();
        cv::namedWindow(DefaultUI::__defualt_ui_window_name);
    }
    DefaultUI::window = cv::Mat(h, w, CV_8UC3, cv::Scalar(0));
    DefaultUI::states = {};
    cv::setMouseCallback(DefaultUI::__defualt_ui_window_name, &__defaultMouseHandler,
                         &DefaultUI::states);
    cv::setWindowTitle(DefaultUI::__defualt_ui_window_name, title);
    DefaultUI::initialized = true;
    DefaultUI::curr_title = title;
}

void DefaultUI::show(Scene &sc, std::string title, std::string desc, bool enable_mouse)
{
    if (!DefaultUI::initialized || DefaultUI::curr_title != title)
    {
        DefaultUI::reset(title, sc.params.width, sc.params.height);
    }
    DefaultUI::states.enable = enable_mouse;
    DefaultUI::states.sc = &sc;
    DefaultUI::states.window_size = make_float2(sc.params.width, sc.params.height);
    const uint width = sc.params.width, height = sc.params.height;
#pragma omp parallel for
    for (int x = 0; x < width; x++)
    {
        for (int y = 0; y < height; y++)
        {
            int idx = y * width + x;
            char4 color = ColorTools::radiance2color(sc.result[idx], 2.2);
            DefaultUI::window.at<cv::Vec3b>(y, x) = cv::Vec3b(color.x, color.y, color.z);
        }
    }
    cv::cvtColor(DefaultUI::window, DefaultUI::window, cv::COLOR_RGB2BGR);
    cv::putText(DefaultUI::window, desc, cv::Point2i(4, 16), cv::FONT_HERSHEY_TRIPLEX, 0.5,
                cv::Scalar(255, 255, 255), 1, 8, false);
    cv::imshow(DefaultUI::__defualt_ui_window_name, DefaultUI::window);
    cv::waitKey(1);
}